using System;
using System.IO;

namespace DeflateLib
{
    /// <summary>
    /// Contains some ZLib (RFC1950) utils for reading/writing 
    /// the format data.
    /// 
    /// To read data: ReadZLibHeader, decomress body, ReadAdler32 and check it.
    /// 
    /// To write data: WriteZLibHeader, compress body, WriteAdler32 for the data.
    /// </summary>
    public static class ZLibUtils
    {
        const int Alder32Base = 65521; /* largest prime smaller than 65536 */

        public const UInt32 Adler32Start = 1;

        public static ZLibHeader ReadZLibHeader(Stream stream)
        {
            if (stream == null) throw new ArgumentNullException("stream");

            int cmf = stream.ReadByte();
            int flg = stream.ReadByte();
            if (cmf < 0 || flg < 0) throw new ZLibException("Unexpected eof of stream");
            if((cmf * 256 + flg) % 31 != 0)
                throw new ZLibException("Invalid header (fcheck)");

            ZLibHeader info = new ZLibHeader();
            info.CompressionMethod = cmf & 0x0F;
            info.CompressionInfo = (cmf >> 4) & 0x0F;
            info.CompressionAlgorithm = (ZLibCompressionAlgorithm)((flg >> 6) & 0x03);
            if ((flg & 0x20) != 0)
                info.DictionaryId = ReadAdler32(stream);
            else
                info.DictionaryId = null;

            return info;
        }

        public static void WriteZLibHeader(Stream stream, ZLibHeader header)
        {
            if (stream == null) throw new ArgumentNullException("stream");
            if (header == null) throw new ArgumentNullException("header");

            if (header.CompressionMethod < 0 || header.CompressionMethod >= 16)
                throw new ArgumentException("Invalid compression method in info");
            if (header.CompressionInfo < 0 || header.CompressionInfo >= 16)
                throw new ArgumentException("Invalid compression info in info");
            if((int)header.CompressionAlgorithm < 0 || (int)header.CompressionAlgorithm >= 4)
                throw new ArgumentException("Invalid compression algorithm in info");

            int cmf = (header.CompressionInfo << 4) | header.CompressionMethod;
            int flg = ((int)header.CompressionAlgorithm << 6);
            if (header.DictionaryId.HasValue) flg |= 0x20;
            int fcheck = 31 - ((cmf * 256 + flg) % 31);
            flg |= fcheck;

            stream.WriteByte((byte)cmf);
            stream.WriteByte((byte)flg);
            if (header.DictionaryId.HasValue)
                WriteAdler32(stream, header.DictionaryId.Value);
        }

        public static UInt32 ReadAdler32(Stream stream)
        {
            if(stream == null) throw new ArgumentNullException("stream");

            byte[] buffer = new byte[4];
            int read = stream.Read(buffer, 0, 4);
            if (read != buffer.Length) throw new ZLibException("Unexpected eof of stream");
            return ((uint)buffer[3]) | (((uint)buffer[2]) << 8)
                | (((uint)buffer[1]) << 16) | (((uint)buffer[0]) << 24);
        }

        public static void WriteAdler32(Stream stream, UInt32 adler)
        {
            if (stream == null) throw new ArgumentNullException("stream");

            stream.WriteByte((byte)(adler >> 24));
            stream.WriteByte((byte)(adler >> 16));
            stream.WriteByte((byte)(adler >> 8));
            stream.WriteByte((byte)(adler));
        }

        public static UInt32 Adler32(byte[] data)
        {
            return Adler32(data, 0, data.Length);
        }

        public static UInt32 Adler32(byte[] data, int index, int length)
        {
            return Adler32(data, index, length, Adler32Start);
        }

        public static UInt32 Adler32(byte[] data, int index, int length, UInt32 adler)
        {
            UInt32 s1 = adler & 0xffff;
            UInt32 s2 = (adler >> 16) & 0xffff;
            int n;

            for (n = 0; n < length; n++) {
                s1 = (s1 + data[n + index]) % Alder32Base;
                s2 = (s2 + s1) % Alder32Base;
            }
            return (s2 << 16) + s1;
        }
    }


    public class ZLibException : Exception
    {
        public ZLibException(string message)
            : base(message)
        {
        }
    }
}
